//
//  Build.swift
//  JKTool
//
//  Created by 姜奎 on 2022/6/22.
//

import Foundation

private struct Options: ParsableArguments {
    
    @Option(name: .long, help: "是否使用缓存，default：true")
    var cache: Bool?
    
    @Option(name: .shortAndLong, help: "代码环境，default：Release")
    var configuration: String?
    
    @Option(name: .shortAndLong, help: "设备类型，default：iOS")
    var sdk: String?
    
    @Option(name: .long, help: "加入模拟器(x86_64)架构，default：false")
    var includedSimulators: Bool?
    
    @Option(name: .long, help: "mac解锁密码（访问钥匙串），Bundle签名时才需要访问mac的钥匙串")
    var macPassword: String?
    
    /*
     xcodebuild -workspace {...}.xcworkspace -target {...} -showBuildSettings  -destination "generic/platform=iOS"
     @Option(name: .shortAndLong, help: ".xcconfig路径")
     var xcconfigPath: String?
     */
    
    @Option(name: .shortAndLong, help: "执行路径")
    var path: String?
    
    @Option(name: .long, help: "build产物副本存储路径【copyPath/{*.a\\*.framework\\*.xcframework\\Header}】")
    var copyPath: String?
    
    @Option(name: .long, help: "使用xcframework，default：false")
    var useXcframework: Bool?
    
    @Option(name: .long, help: "检查本地是否有自定义脚本，若有则执行自定义脚本[{projectPath}/build.sh]")
    var checkCustomBuildScript: Bool?
    
    @Option(name: .shortAndLong, help: "遇到错误时终止，default：false")
    var exitError: Bool = false

    @Option(name: .long, help: "内容格式请参照：JKTool config")
    var configPath: String?

    @Option(name: .long, help: "是否需要加固，default：false")
    var needSec: Bool?

    func customBuildScript() -> String {
        var args = [String]()
        if let cache = cache {
            args.append(contentsOf: ["--cache",String(cache)])
        }
        if let configuration = configuration {
            args.append(contentsOf: ["--configuration",String(configuration)])
        }
        if let sdk = sdk {
            args.append(contentsOf: ["--sdk",String(sdk)])
        }
        
        if let includedSimulators = includedSimulators {
            args.append(contentsOf: ["--included-simulators",String(includedSimulators)])
        }
        
        if let macPassword = macPassword {
            args.append(contentsOf: ["--mac-password",String(macPassword)])
        }
        
        if let copyPath = copyPath {
            args.append(contentsOf: ["--copy-path",String(copyPath)])
        }
        
        if let useXcframework = useXcframework {
            args.append(contentsOf: ["--use-xcframework",String(useXcframework)])
        }
        
        args.append(contentsOf: ["--exit-error",String(exitError)])

        if let configPath = configPath {
            args.append(contentsOf: ["--config-path",String(configPath)])
        }

        if let needSec = needSec {
            args.append(contentsOf: ["--need-sec",String(needSec)])
        }

        return args.joined(separator: " ")
      }
}

extension JKTool {
    struct Build: ParsableCommand {
        static var configuration = CommandConfiguration(
            commandName: "build",
            _superCommandName: "jktool",
            abstract: "build部分命令对于固定工程格式封装",
            version: "1.0.0")
        
        @OptionGroup private var options: Options
        
        mutating func run() {
            
            guard let project = Project.project(directoryPath: options.path ?? FileManager.default.currentDirectoryPath) else {
                return po(tip: "\(options.path ?? FileManager.default.currentDirectoryPath)目录不存在", type: .error)
            }
            
            guard project.rootProject == project,project.recordList.count > 0 else {
                build(project)
               return
            }
            
            let date = Date.init().timeIntervalSince1970
            po(tip: "======build项目开始======")

            // 在执行build时，关闭并行执行策略，减少缓存冲突的发生
            let pool = ThreadPool(maxConcurrency: 1)

            var count = project.recordTree.count

            for records in project.recordTree {
                for record in records {
                    pool.addTask(priority: count, handle: {[self] in
                        guard let pro = Project.project(directoryPath: "\(project.checkoutsPath)/\(record)") else {
                            po(tip: "【\(record)】工程不存在，请检查 Modulefile.recordList 是否为最新内容",type: .warning)
                            return
                        }
                        self.build(pro)
                    })
                }
                count -= 1
            }

            pool.start()
            po(tip: "======build项目完成[\(GlobalConstants.duration(to: date) + " s")]======")
        }
    }
}

extension JKTool.Build {

    static func cleanModuleCache(by projectName: String) {

        let moduleCachePath = "\(FileManager.userDirectory())/Library/Developer/Xcode/DerivedData/ModuleCache.noindex"
        let fileManager = FileManager.default

        // 检查是否是目录
        var isDirectory: ObjCBool = false
        guard fileManager.fileExists(atPath: moduleCachePath, isDirectory: &isDirectory) && isDirectory.boolValue else {
            return
        }

        do {
            // 删除目录及其内容
            try fileManager.removeItem(atPath: moduleCachePath)
            po(tip: "【\(projectName)】清理`~/Library/Developer/Xcode/DerivedData/ModuleCache.noindex `完成。",type: .tip)
        } catch {
            po(tip: "【\(projectName)】清理`~/Library/Developer/Xcode/DerivedData/ModuleCache.noindex `失败。",type: .warning)
        }
    }

    static func cleanDerivedDataCache(by projectName: String, component: String? = nil) {
        let derivedDataPath = "\(FileManager.userDirectory())/Library/Developer/Xcode/DerivedData"
        let fileManager = FileManager.default

        if let component = component, component.isEmpty == false {

            let targetPath = (derivedDataPath as NSString).appendingPathComponent(component)
                // 检查是否是目录
            var isDirectory: ObjCBool = false
            guard fileManager.fileExists(atPath: targetPath, isDirectory: &isDirectory) && isDirectory.boolValue else {
                return
            }

                // 删除目录及其内容
            try? fileManager.removeItem(atPath: targetPath)

            po(tip: "【\(projectName)】清理`~/Library/Developer/Xcode/DerivedData/\(component)`完成。",type: .tip)
            return
        }

        guard let contents = try? fileManager.contentsOfDirectory(atPath: derivedDataPath) else { return }

        let targetPathComponents = contents.filter { item in
            return item.hasPrefix(projectName)
        }

        guard targetPathComponents.isEmpty == false else { return }

        for targetPathComponent in targetPathComponents {

            let targetPath = (derivedDataPath as NSString).appendingPathComponent(targetPathComponent)

                // 检查是否是目录
            var isDirectory: ObjCBool = false
            guard fileManager.fileExists(atPath: targetPath, isDirectory: &isDirectory) && isDirectory.boolValue else {
                continue
            }

                // 删除目录及其内容
            try? fileManager.removeItem(atPath: targetPath)
        }

        po(tip: "【\(projectName)】清理`~/Library/Developer/Xcode/DerivedData/\(projectName)`完成。",type: .tip)
    }

    func build(_ project: Project) {

        let date = Date.init().timeIntervalSince1970
        let configuration = options.configuration ?? "Release"
        let sdk = options.sdk ?? "iOS"

        let status = try? shellOut(to: .gitDiffHEAD(),at: project.directoryPath)
        let commitId = try? shellOut(to: .gitCurrentCommitId(),at: project.directoryPath)

        let xcodeVersion = String.safe(GlobalConstants.xcodeVersion)

        let currentVersion  =  String.safe(commitId)
            .appendingBySeparator(String.safe(status?.MD5) )
            .appendingBySeparator(configuration)
            .appendingBySeparator(sdk)
            .appendingBySeparator(xcodeVersion)
            .appendingBySeparator(SdkType(options.includedSimulators).rawValue)

        if project.recordList.count > 0 {
            _ = try? shellOut(to: .removeFolder(from: project.buildsPath))

            _ = try? shellOut(to: .createFolder(path: project.buildsPath))

            for moduleName in project.recordList {
                guard let subModule = Project.project(directoryPath: project.rootProject.checkoutsPath + "/" + moduleName) else {
                    return po(tip: "\(project.rootProject.buildsPath + "/" + moduleName)目录不存在", type: .error)
                }

                if subModule.vaild() {
                    _ = try? shellOut(to: .createSymlink(to: project.rootProject.buildsPath + "/" + moduleName, at: project.buildsPath))
                } else {
                    _ = try? shellOut(to: .createSymlink(to: project.rootProject.checkoutsPath + "/" + moduleName, at: project.buildsPath))
                }
            }
            po(tip:"【\(project.directoryEntrance)】刷新链接库[\(GlobalConstants.duration(to: date) + " s")]")
        }

        let secProject = sec(project, and: currentVersion)

        if options.checkCustomBuildScript == true, FileManager.default.fileExists(atPath: secProject.directoryPath + "/build.sh") {
            do {
                po(tip:"【\(secProject.directoryEntrance)】执行build.sh")
                let msg = try shellOut(to: ShellOutCommand(string: "chmod +x build.sh && ./build.sh \(secProject.xcodeprojName ?? "") \(configuration) \(sdk) \(options.includedSimulators ?? false) \(secProject.directoryPath) \(options.customBuildScript())"),at: secProject.directoryPath)
                po(tip:"【\(secProject.directoryEntrance)】执行build.sh:\(msg)")
                po(tip: "【\(secProject.directoryEntrance)】执行build.sh 成功",type: .tip)
            } catch  {
                let error = error as! ShellOutError
                po(tip: "【\(secProject.directoryEntrance)】build.sh run error：\n" + error.message + error.output,type: .error)
            }
        } else {
            
            let isRootProject = (secProject == secProject.rootProject)
            let cachePath = isRootProject ? "\(secProject.buildPath)/Universal/Products":"\(secProject.buildPath)/Universal/\(currentVersion)"
            let copyPath = isRootProject ? options.copyPath: (options.copyPath ?? secProject.rootProject.buildsPath)

            if let copyPath = copyPath {
                _ = try? shellOut(to: .createFolder(path: copyPath))
            }
            
            if secProject.vaild() {


                _ = try? shellOut(to: .createFolder(path: secProject.buildPath))

                _ = try? shellOut(to: .createFolder(path: cachePath))

                
                if let copyPath = copyPath {
                    _ = try? shellOut(to: .removeFolder(from: "\(copyPath)/\(secProject.xcodeprojName ?? "")"))
                    _ = try? shellOut(to: .createFolder(path: "\(copyPath)/\(secProject.xcodeprojName ?? "")"))
                }

                secProject.buildTypes?.forEach { buildType in
                    if options.cache == false {
                        po(tip: "【\(secProject.directoryEntrance)】.\(buildType.ext(options.useXcframework)) 不使用缓存，需重新编译！",type: .tip)
                    } else {
                        let needBuild = tryCopyCache(project: secProject, buildType: buildType, cachePath: cachePath, copyPath: copyPath)

                        guard needBuild else { return }
                    }

                    let realMachine = start(project: secProject, buildType: buildType, isSimulators: false)

                    let simulators = start(project: secProject, buildType: buildType, isSimulators: true)

                    creatCache(project: secProject, buildType: buildType, cachePath: cachePath, buildResult: (realMachine, simulators))

                    _ = tryCopyCache(project: secProject, buildType: buildType, cachePath: cachePath, copyPath: copyPath)
                }
                
                do {
                    try FileManager.default.setAttributes([.modificationDate: Date()], ofItemAtPath: cachePath)
                    
                    let fileList = FileManager.default.getFileList(directoryPath: "\(secProject.buildPath)/Universal")?.sorted(by: { file1, file2 in
                        return file1.modificationDate.compare(file2.modificationDate) == .orderedDescending
                    })
                    
                    if let outFirstList = fileList?.suffix(from: 1) {
                        
                        var needRemoveFileList = outFirstList.compactMap{
                            if $0.path.contains("-null-") == false {
                                return $0
                            }
                            return nil
                        }
                        
                        let cacheFileList = outFirstList.compactMap{
                            if $0.path.contains("-null-") == true {
                                return $0
                            }
                            return nil
                        }
                        
                        if cacheFileList.count > 4 {
                            needRemoveFileList = needRemoveFileList + cacheFileList.suffix(from: 4)
                        }
                        
                        for file in needRemoveFileList {
                            try FileManager.default.removeItem(atPath: file.path)
                        }
                    }
                } catch {
                    po(tip: "【\(secProject.directoryEntrance)】缓存管理程序执行异常！",type: .warning)
                }
                
                po(tip:"【\(secProject.directoryEntrance)】build完成[\(GlobalConstants.duration(to: date) + " s")]")
            } else if let copyPath = copyPath {
                po(tip:"【\(secProject.directoryEntrance)】无法检测到可编译内容，将执行link操作")
                _ = try? shellOut(to: .removeFolder(from: "\(copyPath)/\(secProject.directoryEntrance)" ))
                do {
                    try shellOut(to: .createSymlink(to: secProject.directoryPath, at: copyPath))
                } catch  {
                    let error = error as! ShellOutError
                    po(tip: "【\(secProject.directoryEntrance)】copy error：\n" + error.message + error.output,type: .error)
                }
            } else {
                po(tip:"【\(secProject.directoryEntrance)】无法检测到可编译内容[\(GlobalConstants.duration(to: date) + " s")]")
            }
        }
    }

    func sec(_ project: Project, and currentVersion: String) -> Project {

        guard project.vaild() else {
            po(tip: "【\(project.directoryEntrance)】不是xcode工程,跳过加固逻辑！",type: .tip)
            return project
        }

        guard options.needSec == true else {
            po(tip: "【\(project.directoryEntrance)】无需加固[--need-sec != true]！",type: .tip)
            return project
        }

        guard let configPath = options.configPath?.convertRelativePath(absolutPath: project.directoryPath) else {
            po(tip: "【\(project.directoryEntrance)】无需加固[--config-path:\(options.configPath ?? "")]！",type: .tip)
            return project
        }

        guard let secConfig = ProjectConfigModel.configs(configPath)?.secConfig else {
            po(tip: "【\(project.directoryEntrance)】没读到加固配置，跳过加固[--config-path]！",type: .tip)
            return project
        }

        guard secConfig.secModuleList.contains(project.xcodeprojName ?? "") else {
            po(tip: "【\(project.directoryEntrance)】工程不在加固列表中，跳过加固！",type: .tip)
            return project
        }

        guard secConfig.secModuleList.contains(project.xcodeprojName ?? "") else {
            po(tip: "【\(project.directoryEntrance)】不在加固列表中，跳过加固！",type: .tip)
            return project
        }

        let configuration = options.configuration ?? "Release"
        po(tip: "【\(project.directoryEntrance)】读取到加固配置！准备加固。",type: .tip)

        let secConfigPath = secConfig.configFileName.convertRelativePath(absolutPath: configPath)

        let licensePath = secConfig.licenseFileName.convertRelativePath(absolutPath: configPath)

        JKTool.HBBiz.BBSec.main(["--path",project.directoryPath,"--configuration",configuration,"--cache-version",currentVersion,"--sec-version",secConfig.version,"--sec-script-path-name",secConfig.scriptPathName,"--sec-config-path",secConfigPath,"--sec-license-path",licensePath])

        return Project(directoryPath: project.directoryPath + "_sec")
    }

    func tryCopyCache(project: Project, buildType: BuildType, cachePath: String, copyPath: String?) -> Bool {
        let date = Date.init().timeIntervalSince1970
        
        if !FileManager.default.fileExists(atPath: "\(cachePath)/\(buildType.libName()).\(buildType.ext(options.useXcframework))") {
            po(tip: "【\(project.directoryEntrance)】.\(buildType.ext(options.useXcframework)) 缓存不可用，需重新编译！",type: .tip)
            return true
        }
        
        guard let copyPath = copyPath else {
            po(tip: "【\(project.directoryEntrance)】.\(buildType.ext(options.useXcframework)) 缓存可用，不存在目标路径！",type: .warning)
            return false
        }
        
        do {
            switch buildType {
            case .Static(_,_,_):
                try shellOut(to: .copyFile(from: "\(cachePath)/\(buildType.libName()).\(buildType.ext(options.useXcframework))", to: "\(copyPath)/\(project.xcodeprojName ?? "")/\(buildType.name()).\(buildType.ext(options.useXcframework))"))
                if FileManager.default.fileExists(atPath: "\(cachePath)/\(project.xcodeprojName ?? "")") {
                    try shellOut(to: .copyFolder(from: "\(cachePath)/\(project.xcodeprojName ?? "")", to: "\(copyPath)/\(project.xcodeprojName ?? "")"))
                }
                break
            case .Framework(_,_), .Bundle(_,_):
                try shellOut(to: .copyFolder(from: "\(cachePath)/\(buildType.libName()).\(buildType.ext(options.useXcframework))", to: "\(copyPath)/\(project.xcodeprojName ?? "")"))
                break
            }
            
            po(tip: "【\(project.directoryEntrance)】.\(buildType.ext(options.useXcframework)) [\(options.configuration ?? "Release")] 缓存Copy成功[\(GlobalConstants.duration(to: date) + " s")]",type: .tip)
            return false
            
        } catch {
            po(tip: "【\(project.directoryEntrance)】.\(buildType.ext(options.useXcframework)) 缓存重用失败，需重新编译！",type: .warning)
            return true
        }
    }
    
    func start(project: Project, buildType: BuildType, isSimulators: Bool) -> String? {
        let date = Date.init().timeIntervalSince1970
        let configuration = (buildType.isBundle() ? nil: options.configuration) ?? "Release"
        let sdk = options.sdk ?? "iOS"
        
        if isSimulators == true && (buildType.isBundle() == true || options.includedSimulators != true) {
            return nil
        }
        
        let shell = ShellOutCommand.build(scheme: buildType.name(), projectType: project.projectType, configuration: configuration, sdk: sdk, isSimulators: false)
        do {
            let realMachine = try shellOut(to:shell, at: project.directoryPath)
            
            po(tip: "【\(project.directoryEntrance)】.\(buildType.ext()) build命令执行成功[\(GlobalConstants.duration(to: date) + " s")]",type: .tip)
            
            _ = project.writeBuildLog(log: "build command:\n[\(shell.string)]\n" + realMachine, target: buildType, isSimulator: isSimulators)
            return realMachine
        } catch {

            let firstError = error as! ShellOutError

            guard  BuildFailPatternEnum.signatureMismatch.is(firstError.message) else {
                let path = project.writeBuildLog(log: "build command:\n[\(shell.string)]\n" + firstError.message + firstError.output, target: buildType, isSimulator: isSimulators)
                po(tip: "【\(project.directoryEntrance)】.\(buildType.ext()) Build失败，详情(\(path))",type: .error)
                return nil
            }
            // 清理缓存1

            guard let importedByRange = firstError.message.range(of: "imported by '"), let buildRange = firstError.message.range(of: "/Build/") else {
                let path = project.writeBuildLog(log: "build command:\n[\(shell.string)]\n" + firstError.message + firstError.output, target: buildType, isSimulator: isSimulators)
                po(tip: "【\(project.directoryEntrance)】.\(buildType.ext()) Build失败，详情(\(path))",type: .error)
                return nil
            }

                // 提取 "imported by '" 和 "/Build/" 之间的字符串
            let start = importedByRange.upperBound
            let end = buildRange.lowerBound
            let substring = firstError.message[start..<end]

                // 去除前后的斜杠和空格
            let cleanedSubstring = substring.trimmingCharacters(in: .whitespacesAndNewlines)

                // 提取文件夹名称
            guard let folderNameRange = cleanedSubstring.range(of: "DerivedData/") else {
                let path = project.writeBuildLog(log: "build command:\n[\(shell.string)]\n" + firstError.message + firstError.output, target: buildType, isSimulator: isSimulators)
                po(tip: "【\(project.directoryEntrance)】.\(buildType.ext()) Build失败，详情(\(path))",type: .error)
                return nil
            }

            let folderNameStart = folderNameRange.upperBound
            let folderNameEnd = cleanedSubstring.range(of: "/", range: folderNameStart..<cleanedSubstring.endIndex)?.lowerBound ?? cleanedSubstring.endIndex
            let folderName = cleanedSubstring[folderNameStart..<folderNameEnd]

            JKTool.Build.cleanDerivedDataCache(by: project.xcodeprojName ?? "", component: "\(folderName)")
            // 清理缓存2
            JKTool.Build.cleanModuleCache(by: project.xcodeprojName ?? "")

            do {
                let realMachine = try shellOut(to:shell, at: project.directoryPath)

                po(tip: "【\(project.directoryEntrance)】.\(buildType.ext()) build命令执行成功[\(GlobalConstants.duration(to: date) + " s")]",type: .tip)

                _ = project.writeBuildLog(log: "build command:\n[\(shell.string)]\n" + realMachine, target: buildType, isSimulator: isSimulators)
                return realMachine
            } catch {

                let error = error as! ShellOutError
                let path = project.writeBuildLog(log: "build command:\n[\(shell.string)]\n" + error.message + error.output, target: buildType, isSimulator: isSimulators)
                po(tip: "【\(project.directoryEntrance)】.\(buildType.ext()) Build失败，详情(\(path))",type: .error)
                return nil
            }
        }
    }
    
    func creatCache(project: Project, buildType: BuildType, cachePath: String,buildResult: (realMachine: String?, simulators:String?)) {
        switch buildType {
        case .Static(_,_,_):
            cacheStatic(project: project, buildType: buildType, cachePath: cachePath, buildResult: buildResult)
        case .Framework(_,_):
            cacheFramework(project: project, buildType: buildType, cachePath: cachePath, buildResult: buildResult)
        case .Bundle(_,_):
            cacheBundle(project: project, buildType: buildType, cachePath: cachePath, buildResult: buildResult)
        }
    }
    
    func cacheStatic(project: Project, buildType: BuildType, cachePath: String, buildResult: (realMachine: String?, simulators:String?)) {
        let date = Date.init().timeIntervalSince1970
        
        guard  let realMachinePath = BuildSuccessPatternEnum.StaticPath.path(buildResult.realMachine),
               FileManager.default.fileExists(atPath: realMachinePath) else {
            return po(tip: "【\(project.directoryEntrance)】.\(buildType.ext()) 未找到编译成功的真机库文件。",type: .error)
        }
        _ = try? shellOut(to: .copyFolder(from: realMachinePath, to: cachePath))
        
        if let headerPath = BuildSuccessPatternEnum.StaticHeadersPath.path(buildResult.realMachine) {
            _ = try? shellOut(to: .copyFolder(from: headerPath, to: cachePath))
        }
        
        if options.includedSimulators == false {
            guard  let simulatorsPath = BuildSuccessPatternEnum.StaticPath.path(buildResult.simulators),
                   FileManager.default.fileExists(atPath: simulatorsPath) else {
                return po(tip: "【\(project.directoryEntrance)】.\(buildType.ext()) 未找到编译成功的模拟器库文件。",type: .error)
            }
            _ = try? shellOut(to: .staticMerge(source: "\(cachePath)/\(buildType.description)", otherSourcePath: [simulatorsPath]))
        }
        
        po(tip: "【\(project.directoryEntrance)】.\(buildType.ext()) 缓存构建成功[\(GlobalConstants.duration(to: date) + " s")]",type: .tip)
    }
    
    func cacheFramework(project: Project, buildType: BuildType, cachePath: String, buildResult: (realMachine: String?, simulators:String?)) {
        let date = Date.init().timeIntervalSince1970
        
        guard  let realMachinePath = BuildSuccessPatternEnum.FrameworkPath.path(buildResult.realMachine),
               FileManager.default.fileExists(atPath: realMachinePath) else {
            return po(tip: "【\(project.directoryEntrance)】.\(buildType.ext()) 未找到编译成功的真机库文件。",type: .error)
        }
        
        if options.includedSimulators == true {
            
            guard  let simulatorsPath = BuildSuccessPatternEnum.FrameworkPath.path(buildResult.simulators),
                   FileManager.default.fileExists(atPath: simulatorsPath) else {
                return po(tip: "【\(project.directoryEntrance)】.\(buildType.ext()) 未找到编译成功的模拟器库文件。",type: .error)
            }
            
            if options.useXcframework == true {
                _ = try? shellOut(to: .xcframeworkMerge(to: "\(cachePath)/\(buildType.ext(options.useXcframework))", otherSourcePath: [realMachinePath,simulatorsPath]))
            } else {
                _ = try? shellOut(to: .copyFolder(from: realMachinePath, to: cachePath))
                _ = try? shellOut(to: .frameworkMerge(source: "\(cachePath)/\(buildType.description)", otherSourcePath: [simulatorsPath]))
            }
            
        } else {
            if options.useXcframework == true {
                _ = try? shellOut(to: .xcframeworkMerge(to: "\(cachePath)/\(buildType.ext(options.useXcframework))",otherSourcePath: [realMachinePath]))
            } else {
                _ = try? shellOut(to: .copyFolder(from: realMachinePath, to: cachePath))
            }
        }
        po(tip: "【\(project.directoryEntrance)】.\(buildType.ext()) 缓存构建成功[\(GlobalConstants.duration(to: date) + " s")]",type: .tip)
    }
    
    func cacheBundle(project: Project, buildType: BuildType, cachePath: String, buildResult: (realMachine: String?, simulators:String?)) {
        let date = Date.init().timeIntervalSince1970
        guard  let realMachinePath = BuildSuccessPatternEnum.BundlePath.path(buildResult.realMachine),
               FileManager.default.fileExists(atPath: realMachinePath) else {
            return po(tip: "【\(project.directoryEntrance)】.\(buildType.ext()) 未找到编译成功的真机库文件。",type: .error)
        }
        
        _ = try? shellOut(to: .copyFolder(from: realMachinePath, to: cachePath))
        
        po(tip: "【\(project.directoryEntrance)】.\(buildType.ext()) 缓存构建成功[\(GlobalConstants.duration(to: date) + " s")]",type: .tip)
    }
}

extension JKTool.Build {
    struct Clean: ParsableCommand {
        static var configuration = CommandConfiguration(
            commandName: "clean",
            _superCommandName: "jktool",
            abstract: "清理缓存",
            version: "1.0.0")
        
        @Option(name: .shortAndLong, help: "执行路径")
        var path: String?
        
        mutating func run() {
            
            guard let project = Project.project(directoryPath: path ?? FileManager.default.currentDirectoryPath) else {
                return po(tip: "\(path ?? FileManager.default.currentDirectoryPath)目录不存在", type: .error)
            }
            
            guard project.rootProject == project else {
                clean(project)
               return
            }
            
            let date = Date.init().timeIntervalSince1970
            po(tip: "======Clean项目开始======")

            let pool = ThreadPool()

            for record in project.recordList {
                pool.addTask(priority: 1, handle: {[self] in
                    guard let pro = Project.project(directoryPath: "\(project.checkoutsPath)/\(record)") else {
                        po(tip: "\(record) 工程不存在，请检查 Modulefile.recordList 是否为最新内容",type: .warning)
                        return
                    }
                    self.clean(pro)
                })
            }

            pool.addTask(priority: 0) {[self] in
                self.clean(project)
            }

            pool.start()

            JKTool.Build.cleanModuleCache(by: project.xcodeprojName ?? project.directoryEntrance)

            po(tip: "======Clean项目完成[\(GlobalConstants.duration(to: date) + " s")]======")
        }
    }
}

extension JKTool.Build.Clean {
    func clean(_ project: Project) {
        let date = Date.init().timeIntervalSince1970
        // 删除主项目旧相关文件
        if project != project.rootProject {
            _ = try? shellOut(to: .removeFolder(from: project.rootProject.buildsPath + "/" + (project.xcodeprojName ?? "")))
        }
        _ = try? shellOut(to: .removeFolder(from: project.buildPath))

        guard let xcodeprojName = project.xcodeprojName else { return }

        JKTool.Build.cleanDerivedDataCache(by: xcodeprojName)

        po(tip:"【\(project.directoryEntrance)】Clean完成[\(GlobalConstants.duration(to: date) + " s")]")
    }
}
